package com.zhongpengcheng.fortune.es.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.zhongpengcheng.fortune.es.common.pojo.po.GalaxyPO;
import com.zhongpengcheng.fortune.es.common.pojo.po.SingularityPO;
import com.zhongpengcheng.fortune.es.common.pojo.po.ThemePO;
import com.zhongpengcheng.fortune.es.common.pojo.po.VeinPO;
import com.zhongpengcheng.fortune.es.pojo.dto.GalaxyDTO;
import com.zhongpengcheng.fortune.es.pojo.es.GalaxyDO;
import com.zhongpengcheng.fortune.es.pojo.es.StarDO;
import com.zhongpengcheng.fortune.es.service.SeedSearchService;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.InnerHitBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SourceFilter;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Optional;

/**
 * @author ZhongPengCheng
 * @version 1.0
 * @date 2021-10-10 15:41:00
 */
@Service
@Primary
@Slf4j
public class SeedSearchServiceImpl implements SeedSearchService {

    private static final Integer MAX_STAR_COUNT = 64;
    private static final String STAR_PATH = "stars";

    private ElasticsearchRestTemplate client;

    @Override
    public ArrayList<GalaxyDTO> searchSeed(GalaxyPO galaxyPO) {
        // #1 构造查询条件
        QueryBuilder galaxyQuery = buildGalaxyQuery(galaxyPO);
        // #2 构造分页参数
        PageRequest page = galaxyPO.getPage().pageRequest();
        // #3 构造_source参数
        SourceFilter sourceFilter = buildSource();
        // #4 构造最终查询参数
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(galaxyQuery)
                .withPageable(page)
                .withSourceFilter(sourceFilter)
                .build();

        // #5 执行查询
        SearchHits<GalaxyDO> hits = client.search(nativeSearchQuery, GalaxyDO.class);
        log.info("查询成功，total={}", hits.getTotalHits());
        // #6 解析并返回结果
        return parseHits(hits);
    }

    private ArrayList<GalaxyDTO> parseHits(SearchHits<GalaxyDO> hits) {
        ArrayList<GalaxyDTO> galaxyList = new ArrayList<>();

        hits.getSearchHits().forEach(galaxy -> {
            GalaxyDO galaxyDO = galaxy.getContent();
            SearchHits<?> stars = galaxy.getInnerHits(STAR_PATH);
            if (stars == null) {
                return;
            }
            galaxyDO.setStars(new ArrayList<>());
            stars.getSearchHits().forEach(star -> galaxyDO.getStars().add((StarDO) star.getContent()));

            GalaxyDTO galaxyDTO = BeanUtil.copyProperties(galaxyDO, GalaxyDTO.class);
            galaxyDTO.setId(Integer.valueOf(Optional.ofNullable(galaxy.getId()).orElse("-1")));
            galaxyList.add(galaxyDTO);
        });
        return galaxyList;
    }

    /**
     * 构造最顶层查询条件
     */
    private QueryBuilder buildGalaxyQuery(GalaxyPO galaxyPO) {
        return QueryBuilders.boolQuery()
                .filter(buildNestedQuery(galaxyPO));
    }

    /**
     * 构造对stars array的nested查询
     */
    private QueryBuilder buildNestedQuery(GalaxyPO galaxyPO) {
        return QueryBuilders
                .nestedQuery(STAR_PATH, buildStarQuery(galaxyPO), ScoreMode.None)
                .innerHit(buildInnerHits());
    }

    /**
     * 构造实际筛选条件（如大于、小于）
     */
    private QueryBuilder buildStarQuery(GalaxyPO param) {

        return StarQueryBuilder.of(param).toQuery();
    }

    /**
     * 构造_source属性
     */
    private SourceFilter buildSource() {
        return new FetchSourceFilterBuilder()
                .withIncludes("birthPlanetId")
                .build();
    }

    /**
     * 构造内部查询
     */
    private InnerHitBuilder buildInnerHits() {
        return new InnerHitBuilder()
                .setFrom(0)
                .setSize(MAX_STAR_COUNT)
                .setVersion(false)
                .setExplain(false)
                .setTrackScores(false)
                .setIgnoreUnmapped(true);
    }

    @Autowired
    public void setClient(ElasticsearchRestTemplate client) {
        this.client = client;
    }
}

class StarQueryBuilder {
    private final GalaxyPO galaxyPO;

    private final BoolQueryBuilder boolBuilder;

    private StarQueryBuilder(GalaxyPO galaxyPO) {
        this.galaxyPO = galaxyPO;
        boolBuilder = QueryBuilders.boolQuery();
    }

    public static StarQueryBuilder of(GalaxyPO galaxyPO) {
        return new StarQueryBuilder(galaxyPO);
    }

    public QueryBuilder toQuery() {
        Assert.notNull(galaxyPO, "查询参数不能为空");
        return this
                .buildPlanetCount()
                .buildDysonLumino()
                .buildDistance()
                .buildSingularity()
                .buildTheme()
                .buildVein()
                .build();
    }

    private QueryBuilder build() {
        return boolBuilder;
    }


    private StarQueryBuilder buildPlanetCount() {
        Integer planetCount = galaxyPO.getPlanetCount();
        if (planetCount == null || planetCount < 1) {
            return this;
        }
        boolBuilder.filter(QueryBuilders.rangeQuery("stars.planetCount").gte(planetCount));
        return this;
    }

    private StarQueryBuilder buildStarType() {
        Integer starType = galaxyPO.getStarType();
        if (starType == null) {
            return this;
        }
        boolBuilder.filter(QueryBuilders.termQuery("stars.type", starType));
        return this;
    }

    private StarQueryBuilder buildDistance() {
        Integer distance = galaxyPO.getDistance();
        if (distance == null || distance < 1 || distance > 1000) {
            return this;
        }
        boolBuilder.filter(QueryBuilders.rangeQuery("stars.distance").lte(distance * 10));
        return this;
    }

    private StarQueryBuilder buildDysonLumino() {
        Double dysonLumino = galaxyPO.getDysonLumino();
        if (dysonLumino == null || dysonLumino < 0) {
            return this;
        }
        int intValue = Double.valueOf(dysonLumino * 10).intValue();
        boolBuilder.filter(QueryBuilders.rangeQuery("stars.dysonLumino").gte(intValue));
        return this;
    }

    private StarQueryBuilder buildSingularity() {
        SingularityPO singular = galaxyPO.getSingularityQO();
        if (singular == null) {
            return this;
        }
        return this.buildGteIntVal("stars.singularities.tidalLocked", singular.getTidalLocked())
                .buildGteIntVal("stars.singularities.tidalLocked2", singular.getTidalLocked2())
                .buildGteIntVal("stars.singularities.tidalLocked4", singular.getTidalLocked4())
                .buildGteIntVal("stars.singularities.laySide", singular.getLaySide())
                .buildGteIntVal("stars.singularities.clockwiseRotate", singular.getClockwiseRotate())
                .buildGteIntVal("stars.singularities.multipleSatellites", singular.getMultipleSatellites());
    }

    private StarQueryBuilder buildVein() {
        VeinPO veins = galaxyPO.getVeinQO();
        if (veins == null) {
            return this;
        }

        return this.buildBoolVal("stars.veins.fe", veins.getFe())
                .buildBoolVal("stars.veins.cu", veins.getCu())
                .buildBoolVal("stars.veins.si", veins.getSi())
                .buildBoolVal("stars.veins.ti", veins.getTi())
                .buildBoolVal("stars.veins.stone", veins.getStone())
                .buildBoolVal("stars.veins.coal", veins.getCoal())
                .buildBoolVal("stars.veins.oil", veins.getOil())
                .buildBoolVal("stars.veins.fireIce", veins.getFireIce())
                .buildBoolVal("stars.veins.diamond", veins.getDiamond())
                .buildBoolVal("stars.veins.fractal", veins.getFractal())
                .buildBoolVal("stars.veins.crysrub", veins.getCrysrub())
                .buildBoolVal("stars.veins.grat", veins.getGrat())
                .buildBoolVal("stars.veins.bamboo", veins.getBamboo())
                .buildBoolVal("stars.veins.mag", veins.getMag())
                .buildBoolVal("stars.veins.water", veins.getWater())
                .buildBoolVal("stars.veins.sulphuric", veins.getSulphuric());
    }

    private StarQueryBuilder buildTheme() {
        ThemePO themes = galaxyPO.getThemeQO();
        if (themes == null) {
            return this;
        }

        return this.buildBoolVal("stars.themes.ocean", themes.getOcean())
                .buildBoolVal("stars.themes.gas1", themes.getGas1())
                .buildBoolVal("stars.themes.gas2", themes.getGas2())
                .buildBoolVal("stars.themes.gas3", themes.getGas3())
                .buildBoolVal("stars.themes.gas4", themes.getGas4())
                .buildBoolVal("stars.themes.desert1", themes.getDesert1())
                .buildBoolVal("stars.themes.desert2", themes.getDesert2())
                .buildBoolVal("stars.themes.ocean2", themes.getOcean2())
                .buildBoolVal("stars.themes.lava1", themes.getLava1())
                .buildBoolVal("stars.themes.ice1", themes.getIce1())
                .buildBoolVal("stars.themes.desert3", themes.getDesert3())
                .buildBoolVal("stars.themes.desert4", themes.getDesert4())
                .buildBoolVal("stars.themes.volcanic1", themes.getVolcanic1())
                .buildBoolVal("stars.themes.ocean3", themes.getOcean3())
                .buildBoolVal("stars.themes.ocean4", themes.getOcean4())
                .buildBoolVal("stars.themes.ocean5", themes.getOcean5())
                .buildBoolVal("stars.themes.desert5", themes.getDesert5())
                .buildBoolVal("stars.themes.ocean6", themes.getOcean6())
                .buildBoolVal("stars.themes.desert6", themes.getDesert6())
                .buildBoolVal("stars.themes.desert7", themes.getDesert7());
    }

    private StarQueryBuilder buildGteIntVal(String filed, Integer value) {
        if (value == null) {
            return this;
        }
        boolBuilder.filter(QueryBuilders.rangeQuery(filed).gte(value));
        return this;
    }

    private StarQueryBuilder buildBoolVal(String filed, Boolean value) {
        if (value == null) {
            return this;
        }
        boolBuilder.filter(QueryBuilders.termQuery(filed, value));
        return this;
    }
}